package xijia.plus.docs.manage.all.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ZipUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.google.common.collect.Maps;
import io.github.wslxm.springbootplus2.common.auth.util.JwtUtil;
import io.github.wslxm.springbootplus2.core.config.threadpool.XjThreadUtil;
import io.github.wslxm.springbootplus2.core.constant.NumberConst;
import io.github.wslxm.springbootplus2.core.enums.Admin;
import io.github.wslxm.springbootplus2.core.enums.Base;
import io.github.wslxm.springbootplus2.core.utils.BeanDtoVoUtil;
import io.github.wslxm.springbootplus2.core.utils.tree.TreeUtil;
import io.github.wslxm.springbootplus2.core.utils.validated.ValidUtil;
import io.github.wslxm.springbootplus2.file.strategy.context.FileChannelContext;
import io.github.wslxm.springbootplus2.file.strategy.service.FileStrategy;
import io.github.wslxm.springbootplus2.manage.sys.model.dto.MsgDTO;
import io.github.wslxm.springbootplus2.manage.sys.service.DictionaryService;
import io.github.wslxm.springbootplus2.manage.sys.service.MsgService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import xijia.plus.docs.constant.CacheKey;
import xijia.plus.docs.enums.Yw;
import xijia.plus.docs.manage.all.model.config.ExportMdConfig;
import xijia.plus.docs.manage.all.model.config.ImportMdConfig;
import xijia.plus.docs.manage.all.model.entity.DocInfo;
import xijia.plus.docs.manage.all.model.vo.DocInfoVO;
import xijia.plus.docs.manage.all.service.DocCategoryService;
import xijia.plus.docs.manage.all.service.DocInfoService;
import xijia.plus.docs.manage.all.service.ExportService;
import xijia.plus.docs.manage.all.util.MdImgUtil;
import xijia.plus.docs.thymeleafuser.util.XjFileUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

/**
 * 文档表 前端控制器
 *
 * <p>
 * ::本代码由[兮家小二]提供的代码生成器生成,如有问题,请手动修改 ::作者CSDN:https://blog.csdn.net/qq_41463655
 * </p>
 *
 * @author ws
 * @email 1720696548@qq.com 解析
 * @date 2022-10-18 15:11:33
 */
@Slf4j
@Service
//@DS("#header.tenantName")
public class ExportServiceImpl implements ExportService {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private HttpServletResponse response;

    @Autowired
    private DocInfoService docInfoService;

    @Autowired
    private DictionaryService dictionaryService;

    @Autowired
    private MsgService msgService;
    @Autowired
    private DocCategoryService docCategoryService;

    /**
     * 文件渠道
     */
    @Value("${file.channel:LOCAL}")
    private String fileChannel;

    /**
     * 文件渠道 管理器
     */
    @Autowired
    private FileChannelContext fileChannelContext;


    /**
     * 导入文件路径
     */
    String importMdPath = "File/importMd";

    /**
     * 导出文件路径
     */
    String exportMdPath = "File/exportMd";

    /**
     * 默认静态资源路径
     */
    String STATIC = "static";


    @SneakyThrows
    @Override
    @Caching(evict = {
            @CacheEvict(value = CacheKey.XJ_DOC_INFO_TREE, allEntries = true),
            @CacheEvict(value = CacheKey.XJ_DOC_INFO_NAME, allEntries = true)
    })
    @DS("#tenantName")
    public boolean importMd(String tenantName, MultipartFile file, Integer type) {
        // 文档名
        String fullName = JwtUtil.getJwtUser(request).getFullName();
        String userId = JwtUtil.getJwtUser(request).getUserId();
        // 处理前删除之前的处理数据
        FileUtil.clean(new File(importMdPath));

        // 保存上传的压缩文件到服务器
        //file.transferTo(new File())
        XjFileUtil.uploadFile(file, importMdPath);
        String originalFilename = file.getOriginalFilename();

        XjThreadUtil.asyncExecute(() -> {
            // 消息参数
            MsgDTO dto = new MsgDTO();
            dto.setUserId(userId);                               // 接收人用户Id
            dto.setContent("正在进行异步处理导入数据, 请等待完成通知, 导入文档数量越多, 耗时越长");    // 消息内容
            dto.setUserType(Admin.MsgUserType.V2.getValue());    // 终端 1-用户端 2-管理端
            dto.setMsgType(Admin.MsgType.V1.getValue());         // 业务类型 (字典配置, 从字典中读取消息标题以及跳转路由)
            dto.setRouteParams(Maps.newHashMap());               // 路由动态参数
            dto.setIsWebsocket(true);                            // 是否同时推送 websocket 及时消息
            // 发送消息
            msgService.insert(dto);

            // 解压
            ZipUtil.unzip(new File(importMdPath + "/" + originalFilename), Charset.forName("GBK"));
            // 读取解压后的文件目录
            String fileName = originalFilename.substring(0, originalFilename.lastIndexOf("."));
            String basePath = importMdPath + "/" + fileName;
            File files = new File(basePath);

            String categoryName = docCategoryService.findCategoryName(tenantName, type);
            // DictionaryVO dictionaryVO = dictionaryService.findDictCategoryNext("DOC_TYPE", type + "");
            ValidUtil.isTrue(categoryName == null, "导入文档根类型错误");

            // 导入配置
            ImportMdConfig importMdConfig = new ImportMdConfig();
            importMdConfig.setPath(importMdPath);
            importMdConfig.setModuleType(type);
            importMdConfig.setModuleName(fileName);
            importMdConfig.setImgPath("image/media/" + getTimeStr12() + "/");
            importMdConfig.setAuthor(fullName);

            // 递归处理所有文件
            List<DocInfo> docInfos = new ArrayList<>();
            this.importNextMdFile(importMdConfig, files.listFiles(), docInfos, null, "");

            // 更新数据.先删除,在添加
            docInfoService.remove(tenantName, type);
            docInfoService.saveBatch(tenantName, docInfos);

            log.info("文档导入完成, 请查看数据库数据是否完整: {} ", file.getName());

            // 消息参数
            dto = new MsgDTO();
            dto.setUserId(userId);                               // 接收人用户Id
            dto.setContent("文档导入完成, 请检查数据是否正确");       // 消息内容
            dto.setUserType(Admin.MsgUserType.V2.getValue());    // 终端 1-用户端 2-管理端
            dto.setMsgType(Admin.MsgType.V1.getValue());         // 业务类型 (字典配置, 从字典中读取消息标题以及跳转路由)
            dto.setRouteParams(Maps.newHashMap());               // 路由动态参数
            dto.setIsWebsocket(true);                            // 是否同时推送 websocket 及时消息
            // 发送消息
            msgService.insert(dto);
        });
        return true;
    }


    @Override
    @DS("#tenantName")
    public boolean exportMd(String tenantName, Integer type) {

        String userId = JwtUtil.getJwtUser(request).getUserId();
        XjThreadUtil.asyncExecute(() -> {
            // 消息参数
            MsgDTO dto = new MsgDTO();
            dto.setUserId(userId);                               // 接收人用户Id
            dto.setContent("正在进行异步处理导出,请等待完成通知,文档数量越多,可能导致耗时越长");            // 消息内容
            dto.setUserType(Admin.MsgUserType.V2.getValue());    // 终端 1-用户端 2-管理端
            dto.setMsgType(Admin.MsgType.V1.getValue());         // 业务类型 (字典配置, 从字典中读取消息标题以及跳转路由)
            dto.setRouteParams(Maps.newHashMap());               // 路由动态参数
            dto.setIsWebsocket(true);                            // 是否同时推送 websocket 及时消息
            // 发送消息
            msgService.insert(dto);

            // 查询
            List<DocInfo> docInfos = docInfoService.list(tenantName, type);
            ValidUtil.isTrue(docInfos.size() == 0, "该目录下没有文档");
            List<DocInfoVO> btDocInfoVOS = BeanDtoVoUtil.listVo(docInfos, DocInfoVO.class);
            List<DocInfoVO> docTrees = TreeUtil.nextTree(btDocInfoVOS, "0").getChildren();

            // 文档名，= 跟目录
            String categoryName = docCategoryService.findCategoryName(tenantName, type);
            ValidUtil.isTrue(categoryName == null, "导出文档根类型错误");

            ExportMdConfig exportMdConfig = new ExportMdConfig();
            exportMdConfig.setPath(exportMdPath);
            exportMdConfig.setModuleName(categoryName);
            exportMdConfig.setImgPath(exportMdConfig.getPath() + "/" + exportMdConfig.getModuleName() + "/static");

            // 生成前删除之前的文档数据
            FileUtil.clean(new File(exportMdConfig.getPath()));
            // ExportFileUtil.delFileByPath();

            // 生成文档 及 图片到指定目录
            this.exportNextMdFile(exportMdConfig, docTrees, "");

            // 读取目录文件打压缩包
            File file = new File(exportMdConfig.getPath() + "/" + exportMdConfig.getModuleName());
            File zip = ZipUtil.zip(file);
            log.info("文档导出完成, 请刷新 exportMd 查看完整: {} ", file.getName());

            // 消息参数
            dto = new MsgDTO();
            dto.setUserId(userId);                                     // 接收人用户Id
            dto.setContent("文档导出完成, 请点击【导出下载】按钮进行下载");    // 消息内容
            dto.setUserType(Admin.MsgUserType.V2.getValue());          // 终端 1-用户端 2-管理端
            dto.setMsgType(Admin.MsgType.V1.getValue());               // 业务类型 (字典配置, 从字典中读取消息标题以及跳转路由)
            dto.setRouteParams(Maps.newHashMap());                     // 路由动态参数
            dto.setIsWebsocket(true);                                  // 是否同时推送 websocket 及时消息
            // 发送消息
            msgService.insert(dto);
        });
        return true;
    }


    @Override
    @DS("#tenantName")
    public String getExportMdDownloadPath(String tenantName, Integer type) {
        // 文档名 = 跟目录
        String categoryName = docCategoryService.findCategoryName(tenantName, type);
        ValidUtil.isTrue(categoryName == null, "导出文档根类型错误");
        // 获取下载地址
        return exportMdPath + "/" + categoryName + ".zip";
    }

    /**
     * 递归处理导入的Md 文件 保存到
     *
     * @param tree           数据
     * @param exportMdConfig 导出拍照
     * @param pPath          父级目录/..
     * @author wangsong
     * @email 1720696548@qq.com
     * @date 2022/12/21 0021 21:36
     * @version 1.0.0
     */
    private void exportNextMdFile(ExportMdConfig exportMdConfig, List<DocInfoVO> tree, String pPath) {
        if (tree == null) {
            return;
        }
        for (DocInfoVO docInfoVO : tree) {
            // 生成文档
            this.exportCreateMdFile(exportMdConfig, docInfoVO, pPath);
            // 递归生成
            this.exportNextMdFile(exportMdConfig, docInfoVO.getChildren(), pPath + "/" + docInfoVO.getName());
        }
    }

    /**
     * 导出时 创建md 相关文件和文件夹
     *
     * @param exportMdConfig
     * @param docInfoVO
     * @param pPath 父级存储路径
     * @author wangsong
     * @email 1720696548@qq.com
     * @date 2022/12/21 0021 21:38
     * @version 1.0.0
     */
    @SneakyThrows
    private void exportCreateMdFile(ExportMdConfig exportMdConfig, DocInfoVO docInfoVO, String pPath) {
        if (docInfoVO.getShowType().equals(Yw.DocShowType.V1.getValue())) {
            // 目录
            // 创建目录
            // File file = new   pPath
        } else {
            // 计算层级
            String[] hierarchys = pPath.split("/", -1);
            // System.out.println(docInfoVO.getName() + " --> " + hierarchys.length);
            // 文档中图片保存，并替换内容中的图片为相对路径
            String content = this.exportMdImgLocal(exportMdConfig, docInfoVO.getContent(), docInfoVO.getName(), hierarchys.length);
            if (StringUtils.isBlank(content)) {
                content = "暂无内容";
            }
            // 创建文件
            String mdPath = exportMdConfig.getPath() + "/" + exportMdConfig.getModuleName() + "/" + pPath + "/" + docInfoVO.getName() + ".md";
            File file = new File(mdPath);
            FileUtil.appendUtf8String(content, file);
        }
    }

    /**
     * 图片解析 保存到本地
     * 临时保存到 target 目录中的 /static 目录下
     *
     * @param exportMdConfig 生成配置
     * @param markdownStr    文档内容
     * @param name           当前文档名
     * @param hierarchy      当前层级
     * @author wangsong
     * @email 1720696548@qq.com
     * @date 2022/12/22 0022 19:34
     * @version 1.0.0
     */
    private String exportMdImgLocal(ExportMdConfig exportMdConfig, String markdownStr, String name, Integer hierarchy) {
        List<String> imgUrls = MdImgUtil.analysisGetMdImgUrls(markdownStr);
        for (String imgUrl : imgUrls) {
            // 获取文件
            String newImgUrl = MdImgUtil.imgUrlClean(imgUrl);
            String fileName = newImgUrl.substring(newImgUrl.lastIndexOf("/") + 1);
            InputStream inputStream = XjFileUtil.getInputStreamByUrl(newImgUrl, fileName);
            // 读取目录文件打压缩包
            File basePath = null;
            try {
                Path directory = Paths.get(exportMdConfig.getImgPath());
                // 判断目录是否存在，不存在创建
                if (!Files.exists(directory)) {
                    Files.createDirectories(directory);
                }
                // 拷贝文件
                Files.copy(inputStream, directory.resolve(fileName), StandardCopyOption.REPLACE_EXISTING);
                log.info("文档:{}  -->  图片URL: {}  已下载保存到: {}/static 目录下, 文本内容已替换", name, imgUrl, exportMdConfig.getImgPath());
                // 替换内容文本
                String prefix = "";
                for (int i = 1; i < hierarchy; i++) {
                    prefix += "../";
                }
                markdownStr = markdownStr.replace(imgUrl, prefix + "static/" + fileName);
            } catch (Exception e) {
                log.info("文档:{}  -->  图片URL地址: {}  | 替换失败  错误信息: {}", name, imgUrl, null);
            }
        }
        return markdownStr;
    }


    /**
     * 递归获取文件内容
     *
     * @author wangsong
     * @email 1720696548@qq.com
     * @date 2022/12/19 0019 17:36
     * @version 1.0.0
     */
    private void importNextMdFile(ImportMdConfig importMdConfig, File[] fileList, List<DocInfo> docInfos, DocInfo pDocInfo, String pName) {
        if (fileList == null) {
            return;
        }
        for (File file : fileList) {
            DocInfo docInfo = this.importMdContentAnalysis(importMdConfig, file, pDocInfo, pName);
            if (docInfo != null) {
                docInfos.add(docInfo);
            }
            this.importNextMdFile(importMdConfig, file.listFiles(), docInfos, docInfo, pName + "/" + file.getName());
        }
    }


    /**
     * md 中的内容解析
     *
     * @param file
     * @param pDocInfo 当前文档的父级，如果没有父级为 null
     * @return
     * @author wangsong
     */
    private DocInfo importMdContentAnalysis(ImportMdConfig importMdConfig, File file, DocInfo pDocInfo, String pName) {
        // 判断是文件还是文件夹
        boolean directory = file.isDirectory();
        // 文件/文件夹名
        String name = file.getName();
        String id = IdUtil.getSnowflakeNextIdStr();
        DocInfo docInfo = new DocInfo();
        if (directory) {
            // 文件，判断是否静态资源目录
            if (name.toLowerCase().equals(STATIC)) {
                return null;
            }
            // 文件夹
            docInfo.setId(id);
            docInfo.setType(importMdConfig.getModuleType());
            docInfo.setPid(pDocInfo != null ? pDocInfo.getId() : NumberConst.ZERO + "");
            docInfo.setName(name);
            docInfo.setDescribe(null);
            docInfo.setContent(null);
            docInfo.setContentHtml(null);
            docInfo.setAuthor(importMdConfig.getAuthor());
            docInfo.setSort(0);
            docInfo.setShowType(Yw.DocShowType.V1.getValue());
            docInfo.setDisable(Base.Disable.V0.getValue());
            log.info(name);
        } else {
            // 文件，判断是否为 .md 文档
            if (!name.toLowerCase().contains(".md")) {
                return null;
            }
            String content = FileUtil.readUtf8String(file);
            // 处理图片
            content = this.importMdImgAnalysis(importMdConfig, name, content, pName);
            // 处理markdownStr 文本中的图片
            // mdImgAnalysis(content);
            docInfo.setId(id);
            docInfo.setType(importMdConfig.getModuleType());
            docInfo.setPid(pDocInfo != null ? pDocInfo.getId() : NumberConst.ZERO + "");
            docInfo.setName(name.replace(".md", "").replace(".MD", ""));
            docInfo.setDescribe(null);
            docInfo.setContent(content);
            docInfo.setContentHtml(null);
            docInfo.setAuthor(importMdConfig.getAuthor());
            docInfo.setSort(0);
            docInfo.setShowType(Yw.DocShowType.V2.getValue());
            docInfo.setDisable(Base.Disable.V0.getValue());
            log.info("----------" + name);
        }
        return docInfo;
    }


    /**
     * Markdown 文件中的图片解析
     *
     * @return
     * @author wangsong
     */
    private String importMdImgAnalysis(ImportMdConfig importMdConfig, String name, String markdownStr, String pName) {
        List<String> imgUrls = MdImgUtil.analysisGetMdImgUrls(markdownStr);
        for (String imgUrl : imgUrls) {
            String fileNameUrl = MdImgUtil.imgUrlClean(imgUrl);
            String fileName = fileNameUrl.substring(fileNameUrl.lastIndexOf("/") + 1, fileNameUrl.length());
            // 获取文件流
            InputStream inputStream = null;
            try {
                if (imgUrl.indexOf("http") == -1 && imgUrl.indexOf("https") == -1) {
                    // 本地文件 - 随压缩包同时上传时附带的图片
                    // 工具地文件的 ../ 和 ./ 来退层级
                    //String newUrl = imgUrl.replaceAll("\\.\\./", "").replaceAll("\\./", "");
                    String newPname = pName;
                    String newUrl = imgUrl;
                    // 最高可退6层
                    for (int i = 0; i < 6; i++) {
                        if (newUrl.substring(0, 3).equals("../")) {
                            newUrl = newUrl.substring(3);
                            newPname = newPname.substring(0, newPname.lastIndexOf("/"));
                        }
                        if (newUrl.substring(0, 2).equals("./")) {
                            newUrl = newUrl.substring(2);
                            newPname = newPname.substring(0, newPname.lastIndexOf("/"));
                        }
                    }
                    String filePath = importMdConfig.getPath() + "/" + importMdConfig.getModuleName() + "/" + newPname + "/" + newUrl;
                    inputStream = FileUtil.getInputStream(new File(filePath));
                } else {
                    // 可访问的 url地址图片
                    inputStream = XjFileUtil.getInputStreamByUrl(fileNameUrl, fileName);
                }
                // 上传图片
                String url = this.upload(inputStream, importMdConfig.getImgPath(), fileName);
                log.info("文档:{}  -->  替换前: {}  替换后: {} ", name, imgUrl, url);
                // 更新当前内容中的图片地址
                markdownStr = markdownStr.replace(imgUrl, url);

            } catch (Exception e) {
                log.error("文档:{}  -->  图片URL地址: {}  | 替换失败  错误信息: {}", name, imgUrl, e.getMessage());
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return markdownStr;
    }


    /**
     * 上传文件
     *
     * @param inputStream 图片原连接
     * @param imgPath     图片保存地址
     * @param fileName    图片保存名称
     * @return java.lang.String
     * @author wangsong
     */
    private String upload(InputStream inputStream, String imgPath, String fileName) {
        FileStrategy fileStrategy = fileChannelContext.getChannel(fileChannel);
        return fileStrategy.upload(inputStream, imgPath, fileName);
    }


    /**
     * 文件夹拼接随机名
     *
     * @return
     */
    public static String getTimeStr12() {
        String timeStamp = (new SimpleDateFormat("yyyyMMddHHmmss")).format(new Date());
        Random random = new Random();
        for (int i = 0; i < 1; ++i) {
            timeStamp = timeStamp + random.nextInt(10) + "";
        }
        return timeStamp;
    }
}
